#define DEBUG

using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;
using System.Diagnostics;

namespace QuadTerrain
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        //Basic Variables
        static GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Effect effect;
        Camera cCamera;
        VertexDeclaration myVertexDeclaration;
        int screenWidth = 1024;
        int screenHeight = 640;

        //Mouse Input
        gameMouse gameMouse = new gameMouse();
        
        //Shader matrices
        EffectParameter WorldMatrix;
        EffectParameter ViewMatrix;
        EffectParameter ProjectionMatrix;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            //Create an instance of the Camera class
            cCamera = new Camera();
            cCamera.cameraPosition = new Vector3(200, 0, 200);
            cCamera.Pitch = (float)-(Math.PI/2);

            //Set the aspect ratio
            cCamera.aspectRatio = (float)graphics.GraphicsDevice.Viewport.Width / (float)graphics.GraphicsDevice.Viewport.Height;

            //Set graphics varaiables
            graphics.PreferMultiSampling = true;
            graphics.SynchronizeWithVerticalRetrace = false;

            graphics.PreferredBackBufferWidth = screenWidth;
            graphics.PreferredBackBufferHeight = screenHeight;
            //graphics.IsFullScreen = true;

            graphics.ApplyChanges();
            IsFixedTimeStep = false;
          
            base.Initialize();
        }

        public QuadTerrain q;
        public SpriteFont haetten;

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            
            ////////////////////////////////////////////////////////////////////////
            //Load Heightmap as a Texture2D
            Texture2D tMap = Content.Load<Texture2D>("hmap_10243");
            ////////////////////////////////////////////////////////////////////////
            haetten = Content.Load<SpriteFont>("basic");

            effect = Content.Load<Effect>("terrainShader");
            ////////////////////////////////////////////////////////////////////////
            //Create terrain
            q = new QuadTerrain(GraphicsDevice, effect, tMap, 33, 513, 10, 3f);
            ////////////////////////////////////////////////////////////////////////
            //Uncomment this line to save a copy of the normal texture to C:\.
            q.globalNormalTexture.Save(@"C:\normalData\test.png", ImageFileFormat.Png);

            ////////////////////////////////////////////////////////////////////////
            lodLevel = 3.5f;

            //x.Stop();
            //System.Windows.Forms.MessageBox.Show(x.Elapsed.Seconds.ToString());

            myVertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPosition.VertexElements);
           
            ////////////////////////////////////////////////////////////////////////
            //Set various terrain stats.
            q.diffuseScale = q.flatScale / 4;
            q.detailScale = q.flatScale / 100;

            q.detailMapStrength = 2;
            q.textureBlend = Content.Load<Texture2D>("hmap_256blend");
            
            q.textureDetail = Content.Load<Texture2D>("coolgrass2DOT3");
            q.textureRed = Content.Load<Texture2D>("TexR");
            q.textureGreen = Content.Load<Texture2D>("TexG");
            q.textureBlue = Content.Load<Texture2D>("TexB");
            q.textureBlack = Content.Load<Texture2D>("TexBase");
          
            q.sunlightVector = Vector3.Normalize(new Vector3(.5f, .5f, .8f));
            q.sunlightColour = new Vector3(2.3f, 2f, 1.8f);

            //Set Shader Matrices
            WorldMatrix = effect.Parameters["World"];
            ViewMatrix = effect.Parameters["View"];
            ProjectionMatrix = effect.Parameters["Projection"];

            //Use a Fill Mode variable.
            fillM = FillMode.Solid;
            
            GraphicsDevice device = graphics.GraphicsDevice;
            GraphicsDeviceCapabilities caps = device.GraphicsDeviceCapabilities;
            int maxPrimitives = caps.MaxPrimitiveCount;
            int maxIndices = caps.MaxVertexIndex;
            ShaderProfile maxShader = caps.MaxVertexShaderProfile;

        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        float lodLevel;
        float iii;
        float elapsedTimingValue;
        
        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            if (IsActive)
            {
                elapsedTimingValue = (float)gameTime.ElapsedGameTime.TotalMilliseconds / 10f;
                //////////////////////////////////////////////
                iii += 0.002f * elapsedTimingValue;

                float xV3 = (float)Math.Sin(iii);
                float zV3 = (float)Math.Cos(iii);

                Vector3 actualDirection = new Vector3(xV3, .6f, zV3);
                Vector3.Normalize(actualDirection);

                q.sunlightVector = actualDirection;

                // Allows the game to exit
                if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                    this.Exit();

                KeyboardState keyState = Keyboard.GetState();
                // TODO: Add your update logic here

                ////////////////////////////////////////////////////////////////////////
                //Interaction: Move Camera, Sit on top of terrain, etc...
                gameMouse.UpdateMouse(screenWidth, screenHeight);

                cCamera.Yaw += -gameMouse.xSpeed / 200;
                cCamera.Pitch += -gameMouse.ySpeed / 200;

                if (cCamera.Pitch > Math.PI / 2f)
                {
                    cCamera.Pitch = (float)Math.PI / 2f;
                }
                if (cCamera.Pitch < -Math.PI / 2f)
                {
                    cCamera.Pitch = -(float)Math.PI / 2f;
                }


                if (keyState.IsKeyDown(Keys.Escape))
                {
                    Exit();
                }
                if (keyState.IsKeyDown(Keys.W))
                {
                    cCamera.cameraPosition = cCamera.cameraPosition + cCamera.cameraDirection * 4f * elapsedTimingValue;
                }
                if (keyState.IsKeyDown(Keys.A))
                {
                    cCamera.cameraPosition = cCamera.cameraPosition + cCamera.camRotMatrix.Left * 4f * elapsedTimingValue;
                }
                if (keyState.IsKeyDown(Keys.D))
                {
                    cCamera.cameraPosition = cCamera.cameraPosition + cCamera.camRotMatrix.Right * 4f * elapsedTimingValue;
                }
                if (keyState.IsKeyDown(Keys.S))
                {
                    cCamera.cameraPosition = cCamera.cameraPosition + cCamera.camRotMatrix.Backward * 4f * elapsedTimingValue;
                }

                Vector3 positionOnHeightmap = cCamera.cameraPosition;

                //////////////////////////////////////////////////////
                //Use the terrains getHeight Function to work out how to change the camera height.
                float cZ = q.getHeight(positionOnHeightmap.X, positionOnHeightmap.Z);
                {
                    if (!keyState.IsKeyDown(Keys.Space))
                    {
                        if (cCamera.cameraPosition.Y > cZ + 2f)
                        {
                            //Fall
                            cCamera.cameraPosition = new Vector3(cCamera.cameraPosition.X, cCamera.cameraPosition.Y - 1f  * elapsedTimingValue, cCamera.cameraPosition.Z);
                        }
                    }
                    //Remain above the ground
                    if (cCamera.cameraPosition.Y < cZ + 2f)
                    {
                        cCamera.cameraPosition = new Vector3(cCamera.cameraPosition.X, cZ + 2f, cCamera.cameraPosition.Z);
                    }
                    //5 is camera height above ground
                }
                //
                ////////////////////////////////////////////////////////////////////////







                ////////////////////////////////////////////////////////////////////////
                //Increase/decrease dynamic LOD level... etc...
                if (keyState.IsKeyDown(Keys.Q))
                {
                    lodLevel -= 0.01f;
                }
                if (keyState.IsKeyDown(Keys.E))
                {
                    lodLevel += 0.01f;
                }
                cCamera.Updatecamera();
                MouseState MS = Mouse.GetState();

                ////////////////////////////////////////////////////////////////////////
                //Update the Bounding Frustrum...
                BoundingFrustum x = new BoundingFrustum(cCamera.viewMatrix * cCamera.projectionMatrix);
                ////////////////////////////////////////////////////////////////////////
                //... and Update the terrain itself
                
                q.UpdateTerrain(cCamera.cameraPosition, x, lodLevel);
                
                base.Update(gameTime);

                ////////////////////////////////////////////////////////////////////////
                //Depricated
                //Allow the user to turn off detail normal mapping
                //if (keyState.IsKeyDown(Keys.P))
                //{
                //    if (keyPIsDown == false)
                //    {
                //        keyPIsDown = true;
                //        if (detailmapVal != 0)
                //        {
                //            detailmapVal = 0;
                //            q.detailMapStrength = detailmapVal;
                //        }
                //        else
                //        {
                //            detailmapVal = 2;
                //            q.detailMapStrength = detailmapVal;
                //        }
                //    }
                //}
                //else
                //{
                //    keyPIsDown = false;
                //}

                /////////////////////////////////////////////////
                //Change Fill mode
                if (keyState.IsKeyDown(Keys.Tab))
                {
                    if (keyIsDown == false)
                    {
                        keyIsDown = true;
                        if (fillM == FillMode.WireFrame)
                        {
                            fillM = FillMode.Solid;
                        }
                        else
                        {
                            if (fillM == FillMode.Solid)
                            {
                                fillM = FillMode.Point;
                            }
                            else
                            {
                                if (fillM == FillMode.Point)
                                {
                                    fillM = FillMode.WireFrame;
                                }
                            }
                        }
                    }
                }
                else
                {
                    keyIsDown = false;
                }
            }
        }



        FillMode fillM;
        bool keyIsDown;
        bool keyPIsDown;
        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice device = graphics.GraphicsDevice;

            device.Clear(Color.CornflowerBlue);
             
            // TODO: Add your drawing code here

            //Matrix worldMatrix = transforms[mesh.ParentBone.Index];

            WorldMatrix.SetValue(Matrix.Identity);
            ViewMatrix.SetValue(cCamera.viewMatrix);
            ProjectionMatrix.SetValue(cCamera.projectionMatrix);

            graphics.GraphicsDevice.RenderState.FillMode = fillM;

            device.VertexDeclaration = myVertexDeclaration;
         
            //////////////////////////////////////////////////////////
            //////////////////////////////////////////////////////////////////////////
            //Finally, Draw the terrain
            q.DrawTerrain(graphics.GraphicsDevice);


            int totalRenderedIndices = 0;
            for (int i = 0; i < q.Renderedindices.Length; i++)
            {
                totalRenderedIndices+=q.Renderedindices[i];
            }
            //////////////////////////////////////////////////////////////////////////
            //Draw debug data...

            spriteBatch.Begin();
            spriteBatch.DrawString(haetten, "Rendered Primitives: "+(totalRenderedIndices/3).ToString(), new Vector2(20f,20f), Color.Gold);
            spriteBatch.DrawString(haetten, "FPS: "+(Math.Round(FramesPerSecond, 2)).ToString(), new Vector2(20f, 40f), Color.Gold);
            spriteBatch.DrawString(haetten, "LODLevel: " + (Math.Round(lodLevel, 1)).ToString()+"  (q/e)", new Vector2(20f, 60f), Color.Gold);
#if DEBUG
            spriteBatch.DrawString(haetten, "Clear Quadnodes: " + (q.elapsedTicks1 / 100).ToString(), new Vector2(20f, 90f), Color.Gold);
            spriteBatch.DrawString(haetten, "Apply LOD        : " + (q.elapsedTicks2 / 100).ToString(), new Vector2(20f, 110f), Color.Gold);
            spriteBatch.DrawString(haetten, "Prfrm Stitching : " + (q.elapsedTicks4 / 100).ToString(), new Vector2(20f, 130f), Color.Gold);
            spriteBatch.DrawString(haetten, "Compile Array  : " + (q.elapsedTicks5 / 100).ToString(), new Vector2(20f, 150f), Color.Gold);
            spriteBatch.DrawString(haetten, "The Draw Call  : " + (q.elapsedTicks6 / 100).ToString(), new Vector2(20f, 170f), Color.Gold);            
#endif
            spriteBatch.End();

            ////////////////////////////////////////////////////////////////////////
            //Determine FPS
            FramesElapsed++;
            MillisecondsElapsed += gameTime.ElapsedRealTime.Milliseconds;

            if (MillisecondsElapsed > 500)
            {
                FramesPerSecond = FramesElapsed / (MillisecondsElapsed / 1000);
                MillisecondsElapsed -= 500;
                FramesElapsed = 0;
            }

            base.Draw(gameTime);
        }
        static int FramesElapsed = 0;
        static double MillisecondsElapsed = 0;
        static double FramesPerSecond = 0.0f;
    }



















    

    class gameMouse
    {
        public float xSpeed;
        public float ySpeed;

        public void UpdateMouse(int ScreenWidth, int ScreenHeight)
        {
            MouseState mouseState = Mouse.GetState();

            xSpeed = mouseState.X - 320;
            ySpeed = mouseState.Y - 240;

            Mouse.SetPosition(320, 240);
        }
    }
}
